package com.heyu.cgsd.util;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

public class CalcUtils2 {
    private static final String ADD = "+";
    private static final String SUB = "-";
    private static final String MUL = "*";
    private static final String DIV = "/";

    private static boolean isError = false;
    private static boolean isInfinity = false;
    private static boolean isDivider0 = false;

    private static double[] Array = {0.99999999999980993, 676.5203681218851, -1259.1392167224028,
            771.32342877765313, -176.61502916214059, 12.507343278686905,
            -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7};

    public static void main(String[] args) {
        String string = CalcUtils2.scienceCalc2("9.0E-6-6");
        string = CalcUtils2.scienceCalc3(string);
        BigDecimal bd = new BigDecimal(string);
        Double d = bd.doubleValue();
        System.out.println(d);
//        "6(1+2)^(1+2)6!"
        System.out.println(CalcUtils2.scienceCalc2("6(1+2)^(1+2)6!"));
//        System.out.println(CalcUtils2.calcbra("6π(1+2)+(1+3)e"));
    }

    public static String scienceCalc2(String input) {
        // 替换角度°符号
        input = input.replaceAll("°", "");

        if (input.lastIndexOf("＋") == input.length() - 1 || input.lastIndexOf("－") == input.length() - 1
                || input.lastIndexOf("×") == input.length() - 1 || input.lastIndexOf("÷") == input.length() - 1
                || input.lastIndexOf("(") == input.length() - 1) {
            input = input.substring(0, input.length() - 1);
        }

        int len = input.length();
        int bc0 = 0;
        for (int i = 0; i < len; i++) {
            char y = input.charAt(i);
            if (y == '(') {
                bc0++;
            }
            if (y == ')') {
                bc0--;
            }
            if (bc0 < 0) {
                String part1 = input.substring(0, i);
                String part2 = input.substring(i + 1);
                input = part1 + part2;
                len = input.length();
                i--;
                bc0 = 0;
            }
        }

        if (bc0 > 0) {
            StringBuilder ib = new StringBuilder(input);
            for (int j = 0; j < bc0; j++) {
                ib.append(")");
            }
            input = ib.toString();
        }

        len = input.length();

        char x;
        String s;
        int index1 = 0;
        int index2 = 0;
        int index3 = 0;

        if (input.contains("E")) {
            for (int i = 0; i < len; i++) {
                x = input.charAt(i);
                if (x == 'E') {
                    for (int j = i - 1; j >= 0; j--) {
                        char y = input.charAt(j);
                        if (y != '.' && (y < '0' || y > '9')) {
                            index1 = j;
                            break;
                        }
                    }
                    for (int j = i + 1; j < len; j++) {
                        char y = input.charAt(j);

                        if (y != '-' && (y < '0' || y > '9')) {
                            index2 = j;
                            break;
                        }

                        if (y == '-' && j != i + 1) {
                            index2 = j;
                            break;
                        }
                    }

                    if (index1 == 0 && index2 == 0) {
                        BigDecimal bd = new BigDecimal(input);
                        s = bd.toPlainString();
                        input = s;
                    } else if (index1 == 0) {
                        String p2 = input.substring(index2);
                        BigDecimal bd = new BigDecimal(input.substring(0, index2));
                        s = bd.toPlainString();
                        input = s + p2;
                    } else if (index2 == 0) {
                        String p1 = input.substring(0, index1 + 1);
                        BigDecimal bd = new BigDecimal(input.substring(index1 + 1));
                        s = bd.toPlainString();
                        input = p1 + s;
                    } else {
                        String p1 = input.substring(0, index1 + 1);
                        String p2 = input.substring(index2);
                        BigDecimal bd = new BigDecimal(input.substring(index1 + 1, index2));
                        s = bd.toPlainString();
                        input = p1 + s + p2;
                    }

                    len = input.length();
                }
            }
        }

        len = input.length();

        if (input.contains("π")) {
            for (int i = 0; i < len; i++) {
                x = input.charAt(i);
                if (x == 'π') {
                    String part1 = input.substring(0, i);
                    String part2 = input.substring(i + 1);
                    if ("".equals(part1) && "".equals(part2)) {
                        input = "Math.PI";
                    } else if ("".equals(part1)) {
                        input = "Math.PI*" + part2;
                    } else if ("".equals(part2)) {
                        input = part1 + "*Math.PI";
                    } else {
                        input = part1 + "*Math.PI*" + part2;
                    }
                    len = input.length();
                }
            }
        }

        if (input.contains("e")) {
            for (int i = 0; i < len; i++) {
                x = input.charAt(i);
                if (x == 'e') {
                    String part1 = input.substring(0, i);
                    String part2 = input.substring(i + 1);
                    if ("".equals(part1) && "".equals(part2)) {
                        input = "Math.E";
                    } else if ("".equals(part1)) {
                        input = "Math.E*" + part2;
                    } else if ("".equals(part2)) {
                        input = part1 + "*Math.E";
                    } else {
                        input = part1 + "*Math.E*" + part2;
                    }
                    len = input.length();
                }
            }
        }

        if (input.contains("√")) {
            for (int i = 0; i < len; i++) {
                x = input.charAt(i);
                if (x == '√') {
                    int bc = 0;
                    StringBuilder s1 = new StringBuilder();
                    for (int j = i + 1; j < len; j++) {
                        char y = input.charAt(j);
                        if (y == ')') {
                            bc--;
                            if (bc == 0) {
                                s1.append(y);
                                index1 = j + 1;
                                break;
                            }
                        }
                        if (y == '(' && j != i + 1 && bc == 0) {
                            index1 = j - 1;
                            break;
                        } else if (y == '(') {
                            bc++;
                        }
                        if ((y < '0' || y > '9') && bc == 0) {
                            index1 = j;
                            break;
                        }
                        s1.append(y);
                    }
                    String s2 = s1.toString();
                    s = "(Math.sqrt(" + s2 + "))";
                    if (index1 == 0) {
                        input = s;
                    } else {
                        input = s + input.substring(index1);
                    }
                }
            }
        }

        len = input.length();

        if (input.contains("^")) {
            for (int i = 0; i < len; i++) {
                x = input.charAt(i);
                if (x == '^') {
                    int bc1 = 0;
                    int bc2 = 0;
                    StringBuilder s1 = new StringBuilder();
                    StringBuilder s2 = new StringBuilder();
                    for (int j = i - 1; j >= 0; j--) {
                        char y = input.charAt(j);
                        if (y == ')' && j != i - 1 && bc1 == 0) {
                            if (input.charAt(j + 1) == '!') {
                                index1 = j;
                            } else {
                                index1 = j + 1;
                                break;
                            }
                        } else if (y == ')') {
                            bc1++;
                        }
                        if (y == '(') {
                            bc1--;
                            if (bc1 == 0 && input.charAt(j - 1) > '0' && input.charAt(j - 1) < '9') {
                                s1.append(y);
                                index1 = j;
                                break;
                            }
                        }

                        if ((y == '+' || y == '-' || y == '*' || y == '/') && bc1 == 0) {
                            index3 = j + 1;
                            break;
                        }
                        s1.append(y);
                    }
                    String s3 = s1.reverse().toString();
                    for (int j = i + 1; j < len; j++) {
                        char y = input.charAt(j);
                        if (y == ')') {
                            bc2--;
                            if (j < i && input.charAt(j + 1) == '!') {
                                index2 = j + 1;
                            } else {
                                if (bc2 == 0) {
                                    s2.append(y);
                                    index2 = j;
                                    break;
                                }
                            }
                        }
                        if (y == '(' && j != i + 1 && bc2 == 0) {
                            index2 = j - 1;
                            break;
                        } else if (y == '(') {
                            bc2++;
                        }
                        s2.append(y);
                    }
                    String s4 = s2.toString();
                    s = "Math.pow(" + s3 + "&" + s4 + ")";
                    if (index1 == 0 && index2 == 0) {
                        input = s;
                    } else {
                        input = input.substring(0, index1) + s + input.substring(index2 + 1);
                    }
                    len = input.length();
                }
            }
        }

        len = input.length();

        if (input.contains("!")) {
            for (int i = len - 1; i >= 0; i--) {
                x = input.charAt(i);
                if (x == '!') {
                    int bc = 0;
                    StringBuilder s1 = new StringBuilder();
                    for (int j = i - 1; j >= 0; j--) {
                        char y = input.charAt(j);
                        if (y == ')' && j != i - 1 && bc == 0) {
                            index3 = j + 1;
                            break;
                        } else if (y == ')') {
                            bc++;
                        }

                        if (y == '(') {
                            bc--;
                            if (bc < 0) {
                                index3 = j + 1;
                                break;
                            } else if (bc == 0) {
                                s1.append(y);
                                index3 = j;
                                break;
                            } else {
                                index3 = j + 1;
                            }
                        }
                        if (y == ',' || y == '&') {
                            index3 = j + 1;
                            break;
                        }

                        if ((y == '+' || y == '-' || y == '*' || y == '/') && bc == 0) {
                            index3 = j + 1;
                            break;
                        }

                        s1.append(y);
                    }
                    String s3 = s1.reverse().toString();
                    s = "gamma(" + s3 + ")";
                    input = input.substring(0, index3) + s + input.substring(i + 1);
                    len = input.length();
                    i = len - 1;
                }
            }
        }

        return scienceCalc3(input);
    }

    private static String scienceCalc3(String input) {
        input = replaceAll(input);
//        System.out.println(input);
        int index1 = input.lastIndexOf("(");
        int index2 = 0;
        if (index1 != -1) {
            String part1 = input.substring(0, index1);
            StringBuilder sb = new StringBuilder();
            for (int i = index1 + 1; i < input.length(); i++) {
                char x = input.charAt(i);
                if (x == ')') {
                    index2 = i;
                    break;
                }
                sb.append(x);
            }
            String part2 = input.substring(index2 + 1);
            String s = calc(sb.toString());
            if (s.contains("出错")) {
                return "出错";
            }
            String result = part1 + s + part2;
            return scienceCalc3(result);
        }
        if (input.contains("不能除以0")) {
            return "不能除以0";
        }
        return calc(input);
    }

    private static String replaceAll(String oper) {
        oper = oper.replaceAll("＋", ADD);
        oper = oper.replaceAll("－", SUB);
        oper = oper.replaceAll("×", MUL);
        oper = oper.replaceAll("÷", DIV);

        oper = oper.replaceAll(" ", "");
        oper = oper.replaceAll(",", "");

        oper = oper.replaceAll("\\*\\*", MUL);
        oper = oper.replaceAll("\\(\\*", "(");
        oper = oper.replaceAll("\\*\\)", ")");

        oper = oper.replaceAll("([0-9])([(Masctl])", "$1*$2");
        oper = oper.replaceAll("([)])([(0-9asctlM])", "$1*$2");

        return oper;
    }

    /**
     * 该方法完成表达式的计算（不带括弧）
     *
     * @param oper 计算表达式
     * @return 返回运算结果
     */
    public static String calc(String oper) {
        oper = replaceAll(oper);
        //1.将运算表达式分离
        //1.1得到所有参与运算的运算符
        List<Character> op = Oper2op(oper);
        //1.2得到所有的参与运算的数字
        List<Double> cha = Oper2cha(oper);

        if (isInfinity) {
            isInfinity = false;
            return "∞";
        }
        if (isError) {
            isError = false;
            return "出错";
        }

        //遍历运算符容器，完成乘除运算
        for (int i = 0; i < op.size(); i++) {
            Character c = op.get(i);
            if (c == '*' || c == '/') {
                //取得运算符---->把存储的运算符从符号容器中拿出来
                op.remove(i);//是乘除符号则将其从集合中移除出来
                //取得运算数字--->从数字容器中获得对应索引的字符
                Double opl = cha.remove(i);//拿到运算符左侧数字
                Double opr = cha.remove(i);//拿到运算符右侧数字
                //将运算结果放回--->将运算后的数放在数字容器索引出
                if (c == '*') {
                    cha.add(i, opl * opr);//将运算后的数字添加在i的位置，当前i位置的数向后瞬移
                } else {
                    if (opr == 0) {
                        isDivider0 = true;
                        cha.add(i, 0.0);
                    } else {
                        cha.add(i, opl / opr);
                    }
                }
                i--;//运算符容器的指针回到原来的位置,防止跳过下一个运算符
            }
        }
        if (isDivider0) {
            isDivider0 = false;
            return "不能除以0";
        }
        //遍历运算符容器，完成加减运算，当运算符容器为空时，运算结束
        while (!op.isEmpty()) {
            //获取运算符
            Character o = op.remove(0);
            //获取左右运算数字
            Double chal = cha.remove(0);
            Double chara = cha.remove(0);
            if (o == '-') {
                chal = chal - chara;
            }
            if (o == '+') {
                chal = chal + chara;
            }
            //将运算结果放回到数字容器中
            cha.add(0, chal);
        }
        // 防止使用科学记数法
//        BigDecimal bd = new BigDecimal(cha.get(0).toString());

//        return bd.toPlainString();
        return cha.get(0).toString();
    }

    /**
     * 得到表达式中所有的运算符
     *
     * @param oper 运算表达式
     * @return 符号的list集合
     */
    private static List<Character> Oper2op(String oper) {
        //将表达式中的符号替换为@符号
        oper = changeMinus(oper);
        //将字符串按照正则表达式分组
        String regex = "@[0-9A-Za-z.]|[0-9A-Za-z.]";
        Pattern pt = Pattern.compile(regex);
        String[] split = pt.split(oper);
        //将数组元素放在集合中
        List<Character> list = new ArrayList<>();
        for (String aSplit : split) {
            String temp = aSplit.trim();
            if (temp.equals("+") || temp.equals("-") || temp.equals("*") || temp.equals("/")) {
                list.add(temp.trim().charAt(0));
            }
        }
        return list;
    }

    /**
     * 从表达式中获得所有参与运算的数字
     *
     * @param oper 运算表达式
     * @return 数字的List集合
     */
    private static List<Double> Oper2cha(String oper) {
        oper = changeMinus(oper);
        //将字符串按照运算符切割，得到字符串数组
        Pattern pt = Pattern.compile("\\+|\\-|\\*|\\/");
        String[] split = pt.split(oper);

        //遍历数组，判断每个元素，将特殊符号转换成负数符号,并转换成double类型放在list集合中
        List<Double> list = new ArrayList<>();
        for (String aSplit : split) {
            String single = aSplit;
            if (single.contains("@")) {
                if (single.lastIndexOf("@") == single.length() - 1) {
                    single = single.substring(0, single.length() - 2);
                } else {
                    single = single.replaceAll("@", "-");
                }
            }
            if (single.contains("&")) {
                String part1 = single.substring(0, single.indexOf("&"));
                String part2 = single.substring(single.indexOf("&") + 1);
                double d;
                if ("".equals(part1)) {
                    isError = true;
                    d = 0.0;
                } else {
                    d = Math.pow(scienceCalc(part1), scienceCalc(part2));
                }
                list.add(d);
            } else {
                list.add(scienceCalc(single));
            }
        }
        return list;
    }

    /**
     * 将表达式的科学算式先算出来
     *
     * @param oper 运算表达式
     * @return 运算结果
     */
    private static Double scienceCalc(String oper) {
        oper = oper.replaceAll("@", "-");
        oper = oper.replaceAll(",", "");
        double d;
        String part2;

        if (oper.contains("arcsin")) {
            part2 = oper.substring(oper.indexOf("arcsin") + 6);
            if (!"".equals(part2)) {
                double x = Double.valueOf(part2);
                if (x > 1) {
                    isError = true;
                    return 0.0;
                }
                d = Math.toDegrees(Math.asin(x));
            } else {
                d = 0.0;
            }
        } else if (oper.contains("arccos")) {
            part2 = oper.substring(oper.indexOf("arccos") + 6);
            if (!"".equals(part2)) {
                double x = Double.valueOf(part2);
                if (x > 1) {
                    isError = true;
                    return 0.0;
                }
                d = Math.toDegrees(Math.acos(x));
            } else {
                d = 0.0;
            }
        } else if (oper.contains("arctan")) {
            part2 = oper.substring(oper.indexOf("arctan") + 6);
            if (!"".equals(part2)) {
                d = Math.toDegrees(Math.atan(Double.valueOf(part2)));
            } else {
                d = 0.0;
            }
        } else if (oper.contains("sinr")) {
            part2 = oper.substring(oper.indexOf("sinr") + 4);
            if (!"".equals(part2)) {
                d = Math.sin(Double.valueOf(part2) % 360);
            } else {
                d = 0.0;
            }
        } else if (oper.contains("cosr")) {
            part2 = oper.substring(oper.indexOf("cosr") + 4);
            if (!"".equals(part2)) {
                d = Math.cos(Double.valueOf(part2) % 360);
            } else {
                d = 0.0;
            }
        } else if (oper.contains("tanr")) {
            part2 = oper.substring(oper.indexOf("tanr") + 4);
            if (!"".equals(part2)) {
                d = Math.tan(Double.valueOf(part2) % 180);
            } else {
                d = 0.0;
            }
        } else if (oper.contains("sin")) {
            part2 = oper.substring(oper.indexOf("sin") + 3);
            if (!"".equals(part2)) {
                d = Math.sin(Math.toRadians(Double.valueOf(part2) % 360));
            } else {
                d = 0.0;
            }
        } else if (oper.contains("cos")) {
            part2 = oper.substring(oper.indexOf("cos") + 3);
            if (!"".equals(part2)) {
                d = Math.cos(Math.toRadians(Double.valueOf(part2) % 360));
            } else {
                d = 0.0;
            }
        } else if (oper.contains("tan")) {
            part2 = oper.substring(oper.indexOf("tan") + 3);
            if (!"".equals(part2)) {
                d = Math.tan(Math.toRadians(Double.valueOf(part2) % 180));
            } else {
                d = 0.0;
            }
        } else if (oper.contains("lg")) {
            part2 = oper.substring(oper.indexOf("lg") + 2);
            if (!"".equals(part2)) {
                d = Math.log10(Double.valueOf(part2));
            } else {
                d = 0.0;
            }
        } else if (oper.contains("ln")) {
            part2 = oper.substring(oper.indexOf("ln") + 2);
            if (!"".equals(part2)) {
                d = Math.log(Double.valueOf(part2));
            } else {
                d = 0.0;
            }
        } else if (oper.contains("Math.pow")) {
            part2 = oper.substring(oper.indexOf("Math.pow") + 8);
            if (!"".equals(part2)) {
                d = Double.valueOf(part2);
            } else {
                d = 0.0;
            }
        } else if (oper.contains("Math.sqrt")) {
            part2 = oper.substring(oper.indexOf("Math.sqrt") + 9);
            if (!"".equals(part2)) {
                d = Math.sqrt(Double.valueOf(part2));
            } else {
                d = 0.0;
            }
        } else if (oper.contains("gamma")) {
            part2 = oper.substring(oper.indexOf("gamma") + 5);
            if (!"".equals(part2)) {
                Double tmp = Double.valueOf(part2);
                if (tmp.intValue() > 20) {
                    isInfinity = true;
                    return 0.0;
                } else if (tmp.intValue() - tmp == 0) {
                    d = getNFactorial(tmp.intValue());
                } else {
                    d = gamma(tmp + 1);
                }
            } else {
                d = 0.0;
            }
        } else if (oper.contains("Math.PI")) {
            d = Math.PI;
        } else if (oper.contains("Math.E")) {
            d = Math.E;
        } else {
            if (oper.contains(".") && oper.lastIndexOf(".") == oper.length() - 1) {
                oper = oper.substring(0, oper.length() - 1);
            }
            if ("".equals(oper)) {
                oper = "0";
            }
            d = Double.parseDouble(oper);
        }

        return d;
    }

    /**
     * 将表达式的负号‘-’用@符号代替
     *
     * @param oper 运算表达式
     * @return 替换后的表达式
     */
    private static String changeMinus(String oper) {
        //将表达式中的负数符号使用特殊符号替代，获得没有负数字符串
        for (int i = 0; i < oper.length(); i++) {
            char c = oper.charAt(i);
            if (c == '-') {
                //判断第一个字符是否是负数
                if (i == 0) {
                    oper = "@" + oper.substring(1);
                } else {
                    //判断前一个字符是否是+-*/中的一个
                    char cprev = oper.charAt(i - 1);
                    if (cprev == '+' || cprev == '-' || cprev == '*' || cprev == '/'
                            || cprev == '(' || cprev == '^' || cprev == 'g' || cprev == 'n'
                            || cprev == 's' || cprev == '&' || cprev == 'E') {
                        oper = oper.substring(0, i) + "@" + oper.substring(i + 1);
                    }
                }
            }
        }
        return oper;
    }

    /**
     * 整数阶乘
     *
     * @param n 要求的数
     * @return n的阶乘
     */
    private static long getNFactorial(int n) {
        if (n == 0) {
            return 1L;
        }
        return n * getNFactorial(n - 1);
    }

    /**
     * 求小数阶乘
     *
     * @param z 要求的数
     * @return z的阶乘
     */
    private static double gamma(double z) {
        if (z < 0.5) {
            return Math.PI / (Math.sin(Math.PI * z) * gamma(1 - z));
        } else {
            z -= 1;
            double x = Array[0];
            int num = 7;
            for (int i = 1; i < (num + 2); i++) {
                x += Array[i] / (z + i);
            }
            double t = z + num + 0.5;
            return Math.sqrt(2 * Math.PI) * Math.pow(t, (z + 0.5)) * Math.exp(-t) * x;
        }
    }
}
